package com.csii.pe.dynamic.workflow;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.sql.Timestamp;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.csii.common.constants.SystemId;
import com.csii.ibs.Constants;
import com.csii.pe.common.util.BeanUtils;
import com.csii.pe.core.Context;
import com.csii.pe.core.PeException;
import com.csii.pe.core.PeRuntimeException;
import com.csii.pe.dynamic.jnl.JNLService;
import com.csii.pe.dynamic.jnl.JNLState;
import com.csii.pe.dynamic.jnl.QueryJNLService;
import com.csii.pe.dynamic.service.CallbackService;
import com.csii.pe.dynamic.service.ProcessState;
import com.csii.pe.dynamic.service.RequestContext;
import com.csii.pe.dynamic.service.ResponseContext;
import com.csii.pe.dynamic.taskmodel.TaskModel;
import com.csii.pe.dynamic.taskmodel.TaskState;
import com.csii.pe.service.id.IdFactory;
import com.csii.pe.template.AbstractTemplate;
import com.csii.pe.transform.TransformException;
import com.csii.pe.transform.stream.extern.ExternUtil;
import com.csii.pe.validation.ValidationRuntimeException;
import org.springframework.transaction.support.TransactionOperations;

public class TaskEntranceTemplate extends AbstractTemplate {
	private static final Log logger=LogFactory.getLog(TaskEntranceTemplate.class);
	protected TaskModel taskModel;
	protected CallbackServiceRegistry callbackServiceRegistry;
	protected JNLService jnlService;
	protected QueryJNLService queryJnlService;
	
	private IdFactory uuidFactory;
	
	private AccessControlPolicy accessControlPolicy;
	
	public IdFactory getUuidFactory() {
		return uuidFactory;
	}

	public void setAccessControlPolicy(AccessControlPolicy accessControlPolicy) {
		this.accessControlPolicy = accessControlPolicy;
	}

	public void setQueryJnlService(QueryJNLService queryJNLService) {
		this.queryJnlService = queryJNLService;
	}
	
	public void setUuidFactory(IdFactory uuidFactory) {
		this.uuidFactory = uuidFactory;
	}

	public void setJnlService(JNLService jnlService) {
		this.jnlService = jnlService;
	}

	public void setCallbackServiceRegistry(
			CallbackServiceRegistry callbackServiceRegistry) {
		this.callbackServiceRegistry = callbackServiceRegistry;
	}


	public void setTaskModel(TaskModel taskModel) {
		this.taskModel = taskModel;
	}


	protected RequestContext initRequestContext(Context context) throws PeException {
		
		RequestContext requestContext= new RequestContext();
		requestContext.setRequestTransactionId(context.getTransactionId());
		Object mchTimestamp = context.getData("_MCHTimestamp");
		if(mchTimestamp!=null){
			if(mchTimestamp instanceof String)
				requestContext.setRequestMCHTimestamp(Timestamp.valueOf((String)mchTimestamp));
			else if(mchTimestamp instanceof java.util.Date)	{
				if(mchTimestamp instanceof Timestamp)
					requestContext.setRequestMCHTimestamp((Timestamp) mchTimestamp);
				else
					requestContext.setRequestMCHTimestamp(new Timestamp(((java.util.Date)mchTimestamp).getTime()));
			}else{
			  throw new PeException("pe.dms.workflow.invalid_request_header");
			}				
		}
		if(context.getString("_MCHJnlNo")==null){
			throw new ValidationRuntimeException("mca.channel_jnlno_empty");
		}
		requestContext.setRequestMChannelId(context.getString("_MChannelId"));
		requestContext.setRequestMCHJnlNo(context.getString("_MCHJnlNo"));
		requestContext.setRequestLoginType(context.getString("_LoginType"));
		requestContext.setRequestDeptSeq(context.getLong("_DeptSeq"));
		requestContext.setRequestCifSeq(context.getLong("_CifSeq"));
		requestContext.setRequestUserSeq(context.getLong("_UserSeq"));
		requestContext.setRequestProductId(context.getString("_ProductId"));
		requestContext.setRequestServiceId(context.getString("_ServiceId"));
		requestContext.setRequestTerminalId(context.getString("_TerminalId"));
		requestContext.setRequestTimestamp(context.getTimestamp());
		requestContext.setRequestDate(new java.sql.Date(context.getTimestamp().getTime()));
		requestContext.setPreCheck(context.getString("_PreCheck"));
		requestContext.setRequestTokenType(context.getString("_TokenType"));
		requestContext.setAuthLoginId(context.getString("_AuthLoginId"));
		requestContext.setAuthUserPassowrd(context.getString("_AuthUserPassowrd"));

		return requestContext;				
	}
	
	/**
	 * 在此方法中可以预算费用,确定交易认证方式,是否需要临柜授权
	 * @param requestContext
	 * @param request
	 * @param context
	 * @param responseContext
	 */
	protected void precheck(RequestContext requestContext, Object request,Context context, ResponseContext responseContext){
		
	}
	
	/**
	 * 在此方法中可以重新计算费用
	 * @param requestContext
	 * @param request
	 * @param context
	 * @param responseContext
	 */
	protected void beforeSubmit(RequestContext requestContext, Object request,Context context, ResponseContext responseContext){
		
	}
	
	protected void finalHandle(RequestContext requestContext, Object request,Context context, ResponseContext responseContext){
		
	}
	
	@SuppressWarnings("unchecked")
	public void execute(Context context) throws PeException {		
		//initRequestContext
		RequestContext requestContext = initRequestContext(context);
		
		//Check data before further processing.	
		// 获取当前交易码 requestContext.getRequestServiceId() 
		// 返回service交易路径 callbackServiceRegistry.getService
		CallbackService<?,?> service = callbackServiceRegistry.getService(requestContext.getRequestServiceId());
		
		// 当交易不存在时返回报错信息
		if(service==null){
			logger.error("service is not found:"+requestContext.getRequestServiceId());
			throw new PeRuntimeException("mca.service_is_unavailable");
		}
		
		service.setRequestContext(requestContext);
		
		//设置默认的返回上下文
		ResponseContext responseContext= new ResponseContextForMCM(SystemId.MCA.toString());
		
		service.setResponseContext(responseContext);

		//交易预检查
		if(preCheckInner(context, requestContext, service, responseContext)){
			return;
		}
		
		Long jnlSeq=null;
		Map<?, ?> responseMap=null;
		try {
			Object request=null;
			Object authConfig=null;
			Map dataMap = context.getDataMap();
//			dataMap.put("Uuid", aaa);
			dataMap.remove(Constants.JNL_STATUS);
			try{
				//有效性检查及其他需要提交前进行的业务逻辑			
				logger.info("call service prepare method:"+requestContext.getRequestServiceId());
				
				request  = service.prepare(dataMap);//有效性检查
				if(accessControlPolicy!=null){
					accessControlPolicy.permit(requestContext, request, context);
				}
				// 审核模版配置，taskModel不为null该交易需要审核，给当前模版注入taskModel
				if(taskModel!=null){
					// 任务模型预检查
					// 当authConfig为空时，check不通过不需要复核
					authConfig=taskModel.check(request, requestContext);
				}
			}catch(Throwable t){
				//只写标准日志
				responseContext=ExceptionHandler.handleException(context, t);
				dataMap.put(Constants.JNL_STATUS, JNLState.F);
				jnlService.write(requestContext, dataMap, null, request);
				logger.error(t.getMessage(),t);
				return;
			}	
		
			TaskState taskState=null;
			//写交易日志，并写个性化日志（调用回调服务的notify(ProcessState.I, request,requestContext) )
			jnlSeq = jnlService.write(requestContext, dataMap, service, request);
			
			// jnlSeq 为工作流主键
			context.setData("RequestJnlNo", jnlSeq);

			service.setRequestContext(requestContext); //set again for the case when the service copies requestContext instead of using reference.
			
			// 启动工作流服务
			if(taskModel!=null&&authConfig!=null){						
				logger.info("call taskmodel initTask method:"+jnlSeq);	
				try {
					taskState = taskModel.initTask(request, requestContext,authConfig);
				} catch (ValidationRuntimeException e) {
					((CallbackService<Object,?>)service).notify(ProcessState.FL,request, requestContext);
					throw e;
				}
				
				// 非临柜处理流程，
				if (taskState != null&&taskState.isRemoteProcessFlag()) {//临柜授权返回了taskState,这里是非临柜的处理逻辑
					// 调用回调服务，更新当前的个性化日志处理状态为待授权。
					logger.info("call service notify method:"+requestContext.getRequestServiceId()
							+" "+jnlSeq+" "+taskState.getProcessState());
					Object response = ((CallbackService<Object,?>)service).notify(taskState.getProcessState(),request, requestContext);
					if(response!=null){
						responseMap= BeanUtils.bean2Map(response);
						//context.setDataMap(responseMap);
					}
					
					String outObject = null;
					 try {
						outObject = ExternUtil.format(responseMap);
					} catch (TransformException e) {
					} catch (NoSuchAlgorithmException e) {
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					}
			        context.setData("ResponesBean", outObject);
			          
					if(taskState.getProcessState()!=ProcessState.TMR&&taskState.getProcessState()!=ProcessState.WSB){
						//不是预约和待发状态，需进一步授权
						responseContext.setResponseRejCode("CCCCCC");
						String msg=ExceptionHandler.getMessage(context, "mca.next_step_need", null);
						responseContext.setResponseRejMsg(msg);
					}else{
						if(taskState.getMessage()!=null)
							responseContext.setResponseRejMsg(taskState.getMessage());
					}
					//交易需复核或授权,修改当前交易日志状态为完成
					jnlService.updateState(jnlSeq, JNLState.C, responseContext,null,false);
					return;
				}				
			}
			//Submit
			try{
				beforeSubmit(requestContext, request, context, responseContext);
				try{				
					//个性业务的业务状态由callbackService自行完成
					logger.info("call service notify method:"+requestContext.getRequestServiceId()+" "+jnlSeq+" "+ProcessState.SB);
					Object response = ((CallbackService<Object,?>)service).notify(ProcessState.SB,request, requestContext);
					if(response!=null){
						responseMap= BeanUtils.bean2Map(response);
						//context.setDataMap(responseMap);
					}
					
					String outObject = null;
					 try {
						outObject = ExternUtil.format(responseMap);
					} catch (TransformException e) {
					} catch (NoSuchAlgorithmException e) {
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					}
			          context.setData("ResponesBean", outObject);
					
					//reget responseContext. Don't delete
					//注意:responseContext在service的调用过程中会被覆盖,重新取一次
					responseContext = service.getResponseContext();
					
					ReturnMessageResolver.resolveMessage(context, requestContext,responseContext);
				
					JNLState jnlState = JNLState.resolveJNLState(responseContext);
					jnlService.updateState(jnlSeq, jnlState, responseContext,null,true);	
				}finally{
					finalHandle(requestContext, request, context, responseContext);
				}
			}catch(Throwable t){				
				responseContext=ExceptionHandler.handleException(context,t,service.getResponseContext().getResponseSystemId());
				((CallbackService<Object,?>)service).notify(responseContext.getResponseProcessState(),request, requestContext);
				JNLState jnlState = JNLState.resolveJNLState(responseContext);
				jnlService.updateState(jnlSeq, jnlState, responseContext,null,true);
				logger.error(t.getMessage(),t);
			}finally{
				if(taskModel!=null&&taskState!=null&&(!taskState.isRemoteProcessFlag())){
					//临柜复核时需要更新任务状态
					taskModel.notifyState(jnlSeq,ProcessState.SB,responseContext.getResponseProcessState());
				}
			}
		}catch(Throwable t){
			responseContext=ExceptionHandler.handleException(context,t,service.getResponseContext().getResponseSystemId());
			if(jnlSeq!=null){
				JNLState jnlState = JNLState.resolveJNLState(responseContext);
				jnlService.updateState(jnlSeq, jnlState, responseContext,null,true);
			}
			logger.error(t.getMessage(),t);
		}finally{
			finalProcess(context, service, responseContext);
		}
	}

	private void finalProcess(Context context, CallbackService<?, ?> service,ResponseContext responseContext) {
		service.setRequestContext(null);
		Map<?, ?> responseMap = BeanUtils.bean2Map(responseContext);
		context.setData("_ProcessState", responseContext.getResponseProcessState());
		context.setDataMap(responseMap);
	}
	
	private boolean preCheckInner(Context context, RequestContext requestContext,
			CallbackService<?, ?> service, ResponseContext responseContext) {
		boolean preCheckFlag =Boolean.valueOf(requestContext.getPreCheck());
		if(!preCheckFlag){
			return false;
		}
		Object request=null;
		JNLState jnlState=JNLState.C;
		try{
			logger.info("pre cehck:"+requestContext.getRequestServiceId());
			Map<?, ?> dataMap = context.getDataMap();
			request  = service.prepare(dataMap);
			precheck(requestContext, request, context, responseContext);
		}catch(Throwable t){
			logger.error(t.getMessage(),t);
			responseContext=ExceptionHandler.handleException(context,t);
			jnlState=JNLState.resolveJNLState(responseContext);
		}finally{
			if(queryJnlService!=null){
				Long jnlSeq = queryJnlService.write(requestContext, responseContext, context.getDataMap(), request, jnlState);
				context.setData("RequestJnlNo", jnlSeq);
			}
			finalProcess(context, service, responseContext);
		}
		return true;
	}

}
